/*
 * 多租户是Orchard的核心功能，即使您没有注意到，它也在各处被使用。租户基本上是一个子站点，
 * 拥有自己独立的内容和配置，位于自己的域名或URL前缀下。您可以使用租户来托管多个客户的网站，
 * 这些网站来自单个Orchard Core应用程序。这些站点彼此之间一无所知，但它们运行在从同一代码库构建的
 * 同一应用程序中，并可以访问相同的模块和主题。这使得维护此类站点非常高效，无论是托管还是开发。
 * 更多详细信息请查看文档：https://docs.orchardcore.net/en/latest/docs/glossary/#tenant
 *
 * 但是，如果您希望租户不是那么隔离怎么办？如果您有某些内容或配置实际上想要在租户之间共享，
 * 或者某些功能您想要在一个租户上集中化怎么办？您可以使用下面显示的API来跨越租户边界，
 * 并使用来自另一个租户的任何服务！
 *
 * 现在我们要进入相当高级的领域。但别担心，我们会解释一切！
 * 
 * 主要功能：
 * - 演示跨租户服务访问
 * - 展示多租户架构的使用
 * - 演示租户间内容共享
 * - 展示Shell作用域的使用
 */

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement;
using OrchardCore.Environment.Shell;
using System.Threading.Tasks;

namespace Lombiq.TrainingDemo.Controllers;

/// <summary>
/// 跨租户服务控制器 - 演示如何访问其他租户的服务
/// 
/// 主要用途：
/// - 演示跨租户服务访问机制
/// - 展示多租户架构的使用
/// - 演示租户间内容共享
/// - 展示Shell作用域的使用
/// 
/// 使用场景：
/// - 在租户间共享内容
 /* - 集中化某些功能
 * - 跨租户数据访问
 * - 多租户应用管理
 * 
 * 设计原因：
 * - 提供租户间通信机制
 * - 支持共享服务和内容
 * - 实现集中化管理
 * - 展示Orchard Core的高级功能
 * 
 * 设计模式：
 * - 依赖注入模式：通过IShellHost访问服务
 * - 作用域模式：使用Shell作用域管理租户上下文
 * - 服务定位器模式：通过ServiceProvider获取服务
 * - 异步模式：使用async/await处理异步操作
 */
/// </summary>
// 这只是一个为了便于演示的控制器，您可以在任何地方做同样的事情。在Index操作中，
// 我们将使用您已经知道的IContentManager服务从另一个租户获取内容项。这只是一个示例，
// 实际上您也可以访问任何其他服务。
public sealed class CrossTenantServicesController : Controller
{
    private readonly IShellHost _shellHost;

    /// <summary>
    /// 构造函数 - 注入Shell主机服务
    /// </summary>
    /// <param name="shellHost">Shell主机，用于访问其他租户的服务</param>
    // 我们需要IShellHost来访问当前运行的shell的依赖注入容器（Service Provider）中的服务。
    public CrossTenantServicesController(IShellHost shellHost) => _shellHost = shellHost;

    /// <summary>
    /// 跨租户内容访问演示
    /// 
    /// 功能说明：
    /// - 从另一个租户获取内容项
    /// - 演示跨租户服务访问
    /// - 展示Shell作用域的使用
    /// - 返回内容项的标题
    /// 
    /// 访问方式：
    /// - URL：/CrossTenantServices?contentItemId=ID
    /// - ID需要是内容项ID，可以从管理界面编辑项目时的URL获取
    /// 
    /// 参数说明：
    /// - contentItemId：要获取的内容项ID
    /// 
    /// 返回值：
    /// - 成功：返回内容项的标题
    /// - 失败：返回null
    /// </summary>
    /// <param name="contentItemId">内容项ID</param>
    /// <returns>内容项的标题或null</returns>
    // 为了方便的简单路由。您可以从/CrossTenantServices?contentItemId=ID访问此页面。
    // 这里的ID需要是内容项ID，您可以从管理界面打开项目进行编辑时的URL获取（它看起来像"4da2sme18cc2k2r5d4w23d4cwj"，
    // 这不是猫在键盘上走出来的！）。
    [HttpGet, Route("CrossTenantServices")]
    public async Task<string> Index(string contentItemId)
    {
        // 如果ModelState不在有效状态，我们应该中止操作并返回null。
        if (!ModelState.IsValid) return null;

        // 即使您不创建租户，Orchard应用程序中仍然会有一个租户，即Default租户。
        // 对于您创建的所有其他租户，您可以提供技术名称。

        // 在这个示例中，我们将从Default租户访问内容项，但这当然适用于任何租户。
        // 在您的应用程序中创建一个租户（启用Tenants功能，然后从Configuration / Tenants下创建它），
        // 也在其上启用Training Demo，并查看它在那里是如何工作的！

        // 首先，您必须检索包含shell的Service Provider的租户shell作用域。
        // 注意还有一个IShellSettingsManager服务，您可以使用它来访问所有租户的shell设置
        // （shell设置是租户的基本设置，如其技术名称和URL）。
        var shellScope = await _shellHost.GetScopeAsync("Default");

        // 我们只是从这个操作返回内容项的标题，但您也可以对项目做其他任何事情，比如显示它。
        string title = null;

        // 使用UsingAsync()，我们将创建一个块，其中所有内容都在该其他租户的上下文中执行。
        // 这有点类似于在控制器操作内部，但请记住，所有这些都在Default租户上运行，
        // 即使您从您创建的其他租户查看它。
        await shellScope.UsingAsync(async scope =>
        {
            // 您可以从shell的Service Provider解析任何服务。这代替了在构造函数中注入服务。
            var contentManager = scope.ServiceProvider.GetRequiredService<IContentManager>();

            // 我们可以像往常一样使用IContentManager，它只是工作。注意为了简单起见，
            // 这里没有对缺失内容项的错误处理，也没有任何授权。由您来添加这些:)。
            var contentItem = await contentManager.GetAsync(contentItemId);

            if (contentItem != null)
            {
                // DisplayText是您已经在PersonPartHandler中学到的内容。
                title = contentItem.DisplayText;
            }
        });

        return title;
    }
}

// 训练部分结束：从其他租户访问服务

// 下一站：Services/TestedService.cs
